package com.coffee.websocket;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.coffee.core.CoffeeResponseEnum;
import com.coffee.websocket.command.CommandModelMapping;
import com.coffee.websocket.command.MochaBinaryParam;
import com.coffee.websocket.command.MochaCommandModel;
import com.coffee.websocket.command.MochaTextParam;
import com.coffee.websocket.handler.HandlerMapping;
import com.coffee.websocket.handler.MochaBinaryHandler;
import com.coffee.websocket.handler.MochaChatHandler;
import com.coffee.websocket.handler.MochaTextHandler;
import com.coffee.websocket.metadata.MochaBinaryProtocol;
import com.coffee.websocket.metadata.MochaMessageType;
import com.coffee.websocket.metadata.MochaTextProtocol;
import lombok.extern.slf4j.Slf4j;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Map;


/**
 * 通过 userid 建立连接；  再网关已校验 用户合法性；
 * onOpen 建立链接；   onMessage 建立链接之后 client 发送消息服务器接受消息方法；
 * onOpen onMessage 抛出异常 都会到onError 然后到onclose； 【异常 ---> onError ---> onClose】
 */
@ServerEndpoint("/websocket/{userId}")
@Slf4j
public class MochaWebSocketEndpoint {


    @OnOpen
    public void onOpen(Session session, @PathParam("userId") Long userId) throws Exception {
        if (null == session) {
            log.error("connect websocket session is null");
            return;
        }

        if (null == userId || userId.longValue() < 1) {
            log.error("connect websocket userId is null");
            SessionManager.sendMessage(session, MochaWebsocketResponse.responseFailure(CoffeeResponseEnum.LoginFailed));
            session.close();
            return;
        }

        log.info("connect websocket userId {} sessionID {} ", userId, session.getId());

        if (HandlerMapping.getConnectHandler() != null) {
            HandlerMapping.getConnectHandler().connect(userId, session);
        }

        SessionManager.putSession(userId, session);
        log.info("connect websocket success put session;  userId {} sessionID {} ", userId, session.getId());
    }


    @OnClose
    public void onClose(Session session) {
        log.info("close websocket sessionId {}", session.getId());
        Long userId = SessionManager.getKey(session);
        if (null != userId && HandlerMapping.getCloseHandler() != null) {
            HandlerMapping.getCloseHandler().close(userId);
        }
        SessionManager.removeSession(userId);
        log.info("close websocket success remove session: userId {} sessionId {}", userId, session.getId());
    }


    @OnMessage(maxMessageSize = 5242880)
    public void onMessage(ByteBuffer buff, Session session) {
        MochaBinaryProtocol protocol = null;
        try {
            //1 框架校验是否存在
            if (SessionManager.validSession(session)) {
                SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.LoginFailed);
                session.close();
                return;
            }

            if (buff.limit() < 5) {
                log.error("On message bytebuffer limit < 5;  limit: {}", buff.limit());
                SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.ParamInvalid);
                return;
            }

            //buff.mark();

            byte msgType = buff.get();
            MochaMessageType messageType = MochaMessageType.codeOf(msgType);
            if (null == messageType){
                SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.ParamInvalid);
                log.error("MochaMessageType {} not find [1,2] ", msgType);
            }

            int cmd = buff.getInt();
            int state = buff.getInt();
            MochaCommandModel commandModel = CommandModelMapping.getCommandModel(cmd);
            if (null == commandModel) {
                log.error("On message not mapping commandModel cmd: {}", cmd);
                SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.NotPermission);
                return;
            }

            //2, 业务校验
            if (commandModel.isVerifyType()){
                if (HandlerMapping.getUserValidHandler() != null &&  HandlerMapping.getUserValidHandler().verifyInvalid(SessionManager.getKey(session))) {
                    SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.LoginFailed);
                    session.close();
                    return;
                }
            }

            protocol = new MochaBinaryProtocol(msgType, cmd, state);

            MochaBinaryParam binaryParam = new MochaBinaryParam(protocol);
            binaryParam.setSession(session);
            binaryParam.setUserId(SessionManager.getKey(session));
            binaryParam.setRequest(commandModel.getBinaryRequest().decode(buff));

            //buff.flip();

            if (messageType.equals(MochaMessageType.Chat)){
                MochaChatHandler handler = HandlerMapping.getChatHandler(cmd);
                if (null == handler) {
                    log.error("On message not mapping chat handler cmd: {}", cmd);
                    protocol.setState(CoffeeResponseEnum.NotHandler.code());
                    SessionManager.sendMessage(session, protocol);
                    return;
                }
                handler.handle(binaryParam);
            }else {
                MochaBinaryHandler handler = HandlerMapping.getBinaryHandler(cmd);
                if (null == handler) {
                    log.error("On message not mapping binary handler cmd: {}", cmd);
                    protocol.setState(CoffeeResponseEnum.NotHandler.code());
                    SessionManager.sendMessage(session, protocol);
                    return;
                }
                handler.handle(binaryParam);
            }

        }catch (BufferUnderflowException e){
            SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.ParamInvalid);
            log.error("maybe message error: ", e);
        }catch (MochaWebsocketException me) {
            SessionManager.sendStateMessage(session, protocol, me.getState());
            log.error("biz  error: ", me);
        } catch (Exception e) {
            SessionManager.sendStateMessage(session, protocol, CoffeeResponseEnum.SysException);
            log.error("sys  error: ", e);
        }
    }


    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        log.debug("onMessage  message: {} - session: {} ", message, session.getId());

        //1 框架校验是否存在
        if (SessionManager.validSession(session)) {
            SessionManager.sendMessage(session, MochaWebsocketResponse.responseFailure(CoffeeResponseEnum.LoginFailed));
            session.close();
            return;
        }
        MochaTextProtocol mochaMessage = null;
        try {

            mochaMessage = JSON.parseObject(message, MochaTextProtocol.class);
            if (null == mochaMessage || mochaMessage.getRequest() == null || mochaMessage.getCmd() == 0) {
                log.error("On message json error {}", message);
                SessionManager.sendMessage(session, MochaWebsocketResponse.response(message, CoffeeResponseEnum.ParamInvalid));
                return;
            }

            MochaCommandModel commandModel = CommandModelMapping.getCommandModel(mochaMessage.getCmd());
            if (null == commandModel) {
                log.error("On message not mapping commandModel {}", message);
                SessionManager.sendMessage(session, MochaWebsocketResponse.response(mochaMessage.getCmd(), message, CoffeeResponseEnum.NotPermission));
                return;
            }

            //2, 业务校验
            if (commandModel.isVerifyType()){
                if (HandlerMapping.getUserValidHandler() != null &&  HandlerMapping.getUserValidHandler().verifyInvalid(SessionManager.getKey(session))) {
                    SessionManager.sendMessage(session, MochaWebsocketResponse.responseFailure(CoffeeResponseEnum.LoginFailed));
                    session.close();
                    return;
                }
            }

            MochaTextHandler handler = HandlerMapping.getTextHandler(mochaMessage.getCmd());
            if (null == handler) {
                log.error("On message not mapping handler {}", message);
                SessionManager.sendMessage(session, MochaWebsocketResponse.response(mochaMessage.getCmd(), message, CoffeeResponseEnum.NotHandler));
                return;
            }

            MochaTextParam mochaCommand = new MochaTextParam();
            mochaCommand.setCmd(mochaMessage.getCmd());
            mochaCommand.setUserId(SessionManager.getKey(session));
            mochaCommand.setRequest(mapToBean(mochaMessage.getRequest(), commandModel.getTextRequest().getClass()));
            if (null == mochaCommand.getRequest()) {
                log.error("On message request error {}", message);
                SessionManager.sendMessage(session, MochaWebsocketResponse.response(mochaMessage.getCmd(), message, CoffeeResponseEnum.ParamInvalid));
                return;
            }
            mochaCommand.setSession(session);
            handler.handle(mochaCommand);

        } catch (JSONException ite) {
            //  前端数据格式不对
            SessionManager.sendMessage(session, MochaWebsocketResponse.response(message, CoffeeResponseEnum.ParamInvalid));
            log.error("maybe message error: " + message, ite);
        } catch (MochaWebsocketException me) {
            SessionManager.sendMessage(session, MochaWebsocketResponse.response(me.getCmd(), me.getMessage(), me.getState()));
            log.error("biz  error: ", me);
        } catch (Exception e) {
            SessionManager.sendMessage(session, MochaWebsocketResponse.response(null != mochaMessage ? mochaMessage.getCmd() : 0, message, CoffeeResponseEnum.SysException));
            log.error("sys  error: ", e);
        }

    }


    @OnError
    public void onError(Session session, Throwable error) {
        log.error("Error {}", error.getMessage());
        if (null != session) {
            if (error instanceof MochaWebsocketException) {
                MochaWebsocketException me = (MochaWebsocketException) error;
                SessionManager.sendMessage(session, MochaWebsocketResponse.response(me.getCmd(), me.getMessage(), me.getState()));
            } else {
                SessionManager.sendMessage(session, MochaWebsocketResponse.responseFailure(CoffeeResponseEnum.SysException));
            }
        }
    }


    public static <T> T mapToBean(Map<String, Object> map, Class<T> beanClass) throws InstantiationException, IllegalAccessException {
        T object = beanClass.newInstance();
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            int mod = field.getModifiers();
            if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
                continue;
            }
            field.setAccessible(true);
            if (map.containsKey(field.getName())) {
                field.set(object, map.get(field.getName()));
            }
        }
        return object;
    }


}